package org.lex.depends.core.classloader;

import java.io.IOException;
import java.net.URL;
import java.security.CodeSigner;
import java.security.CodeSource;
import java.security.SecureClassLoader;
import java.util.Enumeration;

import sun.misc.Resource;
import sun.misc.URLClassPath;

/**
 * This class loader is used to load classes and resources from a search path of
 * URLs referring to both JAR files and directories. Any URL that ends with a
 * '/' is assumed to refer to a directory. Otherwise, the URL is assumed to
 * refer to a JAR file which will be opened as needed.
 * <p>
 * The AccessControlContext of the thread that created the instance of
 * URLClassLoader will be used when subsequently loading classes and resources.
 * <p>
 * The classes that are loaded are by default granted permission only to access
 * the URLs specified when the URLClassLoader was created.
 * 
 * @author David Connelly
 * @version 1.91, 04/06/07
 * @since 1.2
 */
public class MyClassLoader extends SecureClassLoader {
	/* The search path for classes and resources */
	URLClassPath ucp;

	/**
	 * Constructs a new URLClassLoader for the given URLs. The URLs will be
	 * searched in the order specified for classes and resources after first
	 * searching in the specified parent class loader. Any URL that ends with a
	 * '/' is assumed to refer to a directory. Otherwise, the URL is assumed to
	 * refer to a JAR file which will be downloaded and opened as needed.
	 * 
	 * <p>
	 * If there is a security manager, this method first calls the security
	 * manager's <code>checkCreateClassLoader</code> method to ensure creation
	 * of a class loader is allowed.
	 * 
	 * @param urls
	 *            the URLs from which to load classes and resources
	 * @param parent
	 *            the parent class loader for delegation
	 * @exception SecurityException
	 *                if a security manager exists and its
	 *                <code>checkCreateClassLoader</code> method doesn't allow
	 *                creation of a class loader.
	 * @see SecurityManager#checkCreateClassLoader
	 */
	public MyClassLoader(URL[] urls, ClassLoader parent) {
		super(parent);
		// this is to make the stack depth consistent with 1.1
		SecurityManager security = System.getSecurityManager();
		if (security != null) {
			security.checkCreateClassLoader();
		}
		ucp = new URLClassPath(urls);
	}

	/**
	 * Finds and loads the class with the specified name from the URL search
	 * path. Any URLs referring to JAR files are loaded and opened as needed
	 * until the class is found.
	 * 
	 * @param name
	 *            the name of the class
	 * @return the resulting class
	 * @exception ClassNotFoundException
	 *                if the class could not be found
	 */
	protected Class<?> findClass(final String name) throws ClassNotFoundException {
		String path = name.replace('.', '/').concat(".class");
		Resource res = ucp.getResource(path, false);
		if (res != null) {
			try {
				return defineClass(name, res);
			} catch (IOException e) {
				throw new ClassNotFoundException(name, e);
			}
		} else {
			throw new ClassNotFoundException(name);
		}
	}

	/*
	 * Defines a Class using the class bytes obtained from the specified
	 * Resource. The resulting Class must be resolved before it can be used.
	 */
	private Class<?> defineClass(String name, Resource res) throws IOException {
		int i = name.lastIndexOf('.');
		URL url = res.getCodeSourceURL();
		// Now read the class bytes and define the class
		java.nio.ByteBuffer bb = res.getByteBuffer();
		if (bb != null) {
			// Use (direct) ByteBuffer:
			CodeSigner[] signers = res.getCodeSigners();
			CodeSource cs = new CodeSource(url, signers);
			return defineClass(name, bb, cs);
		} else {
			byte[] b = res.getBytes();
			// must read certificates AFTER reading bytes.
			CodeSigner[] signers = res.getCodeSigners();
			CodeSource cs = new CodeSource(url, signers);
			return defineClass(name, b, 0, b.length, cs);
		}
	}

	/**
	 * Finds the resource with the specified name on the URL search path.
	 * 
	 * @param name
	 *            the name of the resource
	 * @return a <code>URL</code> for the resource, or <code>null</code> if the
	 *         resource could not be found.
	 */
	public URL findResource(final String name) {
		/*
		 * The same restriction to finding classes applies to resources
		 */
		URL url = (URL) ucp.findResource(name, true);
		return url != null ? ucp.checkURL(url) : null;
	}

	/**
	 * Returns an Enumeration of URLs representing all of the resources on the
	 * URL search path having the specified name.
	 * 
	 * @param name
	 *            the resource name
	 * @exception IOException
	 *                if an I/O exception occurs
	 * @return an <code>Enumeration</code> of <code>URL</code>s
	 */
	public Enumeration<URL> findResources(final String name) throws IOException {
		return ucp.findResources(name, true);
	}
}
